package cnt5106.p2p.tracker;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Vector;

import cnt5106.p2p.common.protocol.Message;
import cnt5106.p2p.common.protocol.MessageType;

public class TrackerServer {

	public TrackerServer(String hostname, int port) throws IOException {
		serverSocket = new DatagramSocket(null);
		dataManager = new DataManager();

		serverSocket.bind(new InetSocketAddress(hostname, port));
		System.out.println("Tracker is started.");
		System.out.println("Address: " + serverSocket.getLocalSocketAddress());
	}

	public TrackerServer(String hostname) throws IOException {
		this(hostname, 0);
	}

	public TrackerServer() throws IOException {
		this("localhost", 0);
	}

	public void start() {
		System.out.println("Server is running!");
		while (true) {
			try {
				DatagramPacket packet = new DatagramPacket(buffer, BUFFER_SIZE);
				serverSocket.receive(packet);

				Message request = Message.parse(packet.getData());
				System.out.println("[" + String.format("%tc", Calendar.getInstance())
						+ "]Received message from " + packet.getSocketAddress()
						+ ": " + request);
				handleMessage(Message.parse(Arrays.copyOfRange(
						packet.getData(), 0, packet.getLength())),
						packet.getSocketAddress());

			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private void handleMessage(Message msg, SocketAddress replyEndpoint) {
		Message reply = null;
		switch (msg.getType()) {
		case TRACKER_REGISTER_PEER:
			if (dataManager.hasFile(msg.getFilename())) {
				reply = Message.generate(
						MessageType.TRACKER_DUPLICATE_FILENAME,
						msg.getFilename(), 0, null);
				sendReply(reply, replyEndpoint);
			} else {
				// Create a new record
				dataManager.registerPeer(msg.getFilename(),
						(InetSocketAddress) msg.getData(), msg.getNumber());

				// Reply register peer message
				reply = Message.generate(MessageType.TRACKER_REPLY_PEER,
						msg.getFilename(), 0, null);
				sendReply(reply, replyEndpoint);
			}
			break;
		case TRACKER_REGISTER_GROUP:
			if (dataManager.hasFile(msg.getFilename())) {
				// Add to list
				dataManager.registerGroup(msg.getFilename(),
						(InetSocketAddress) msg.getData());

				// Reply register group message
				reply = Message.generate(MessageType.TRACKER_REPLY_GROUP,
						msg.getFilename(),
						dataManager.getFileSize(msg.getFilename()),
						dataManager.getPeerList(msg.getFilename()));
				sendReply(reply, replyEndpoint);

				// Notify new peer's endpint to other peers
				Vector<InetSocketAddress> peerList = dataManager
						.getPeerList(msg.getFilename());
				Message bcastMsg = Message.generate(
						MessageType.TRACKER_NOTIFY_NEW_PEER, msg.getFilename(),
						1, msg.getData());
				for (InetSocketAddress endpoint : peerList) {
					if (!endpoint.equals(msg.getData())) {
						sendReply(bcastMsg, endpoint);
					}
				}
			} else {
				reply = Message.generate(MessageType.TRACKER_FILE_NOT_FOUND,
						msg.getFilename(), 0, null);
				sendReply(reply, replyEndpoint);
			}
			break;
		case TRACKER_LIST:
			Vector<String> fileList = dataManager.list();
			reply = Message.generate(MessageType.TRACKER_REPLY_LIST, null,
					fileList.size(), fileList);
			sendReply(reply, replyEndpoint);
			break;
		default:
			// Do nothing
		}
	}

	public SocketAddress getSocketAddress() {
		return serverSocket.getLocalSocketAddress();
	}

	private void sendReply(Message msg, SocketAddress replyEndpoint) {
		try {
			serverSocket.send(new DatagramPacket(msg.toByte(),
					msg.toByte().length, replyEndpoint));
			System.out.println("[" + String.format("%tc", Calendar.getInstance()) + "]Send reply to " + replyEndpoint + ": " + msg);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private DatagramSocket serverSocket;
	private final int BUFFER_SIZE = 8192;
	private byte[] buffer = new byte[BUFFER_SIZE];
	private DataManager dataManager = null;
}
